﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ExtensionLoader;
using Nwc.XmlRpc;
using HttpServer;
using System.Collections;
using CableBeachMessages;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
using OpenMetaverse.Http;

namespace WorldServer.Extensions
{
    public class RexLogin : OpenSimLogin
    {
        public override bool Start(WorldServer server)
        {
            this.server = server;

            if (!LoadConfiguration())
                return false;

            server.XmlRpcProvider.AddXmlRpcHandler(@"^/$", "login_to_simulator", RexLoginMethod);

            return true;
        }

        XmlRpcResponse RexLoginMethod(XmlRpcRequest request, IHttpRequest httpRequest)
        {
            Hashtable requestData = (Hashtable)request.Params[0];

            string clientVersion = "Unknown";
            if (requestData.Contains("version"))
            {
                clientVersion = (string)requestData["version"];
            }

            if (!clientVersion.StartsWith("realXtend"))
            {
                Logger.InfoFormat("[realXtendLogin] client loggin in is not realXtend client. Client version {0} send to OpenSim login", clientVersion);
                return base.LoginMethod(request, httpRequest);
            }

            string account;
            string sessionHash;
            string startLocationRequest = "last";
            bool GoodLogin = false;
            if (requestData.Contains("account") && requestData.Contains("sessionhash")) //realXtend style login
            {
                account = (string)requestData["account"];
                sessionHash = (string)requestData["sessionhash"];
                if (requestData.Contains("start"))
                {
                    startLocationRequest = (string)requestData["start"];
                }

                GoodLogin = AuthenticateUser(account, sessionHash);
            }
            else //OpenSim/LL style
            {
                XmlRpcResponse rep = base.LoginMethod(request, httpRequest);
                Hashtable val = (Hashtable)rep.Value;
                val["rex"] = "running rex mode";
                //val["sim_port"] = GetPort(m_primaryRegionInfo.RegionHandle);
                //val["region_x"] = (Int32)(m_primaryRegionInfo.RegionLocX * 256);
                //val["region_y"] = (Int32)(m_primaryRegionInfo.RegionLocY * 256);
                return rep;
            }

            if (!GoodLogin)
            {
                return LindenLoginHelper.CreateLoginFailedResponse();
            }
            else
            {
                //TODO: insert magic
                UUID sessionID;
                string actName = account.Split('@')[0];
                string actSrv = account.Split('@')[1];

                Uri identity = new Uri(server.HttpUri, "/users/" + actName + "." + actSrv);

                #region Discovery

                // Create an empty list of services, since the client can't specify its
                // own services through the XML-RPC login
                ServiceCollection services = new ServiceCollection();

                // Get a list of services to attach to this avatar. This will also contact any trusted
                // services to attempt capability fetching
                server.ServiceProvider.GetServices(identity, true, ref services);

                // Since the Linden viewer has no way of assisting in service discovery or performing
                // an OAuth handshake, any missing service capabilities at this point means a failure
                foreach (Service service in services.Values)
                {
                    foreach (KeyValuePair<Uri, Uri> serviceMethod in service.Capabilities)
                    {
                        if (serviceMethod.Value == null)
                        {
                            Logger.Error("[OpenSimLogin] Login failed for " + actName + " " + actSrv +
                                ", missing service endpoint for capability " + serviceMethod.Key);
                            return LindenLoginHelper.CreateLoginServicesErrorResponse();
                        }
                    }
                }

                #endregion Discovery

                // Check if this avatar is already logged in. If so, destroy the old session
                if (server.SessionProvider.TryGetSession(identity, out sessionID))
                    LogoutAvatar(sessionID);

                // Create an avatar object
                Avatar avatar = RexLoginHelper.GetUserByAccount(actName, actSrv);
                avatar.Identity = identity;

                // Create a new session
                sessionID = server.SessionProvider.CreateSession(avatar);

                //add services for avatar
                avatar.Services = services;
                
                // Update last login timestamp
                avatar.Attributes[AvatarAttributes.LAST_LOGIN_DATE] = OSD.FromDate(DateTime.Now);
                server.AttributeProvider.UpdateAttributes(avatar.Identity, avatar.Attributes);

                LindenLoginData response = new LindenLoginData();

                //response.ActiveGestures
                response.AgentID = avatar.ID;
                response.BuddyList = LindenLoginHelper.GetBuddyList(avatar.ID);
                LindenLoginHelper.SetClassifiedCategories(ref response);
                response.FirstName = avatar.GetAttribute(AvatarAttributes.FIRST_NAME).AsString() + " " + avatar.GetAttribute(AvatarAttributes.LAST_NAME).AsString();
                avatar.Attributes[AvatarAttributes.FIRST_NAME] = OSD.FromString(response.FirstName);
                response.HomeLookAt = avatar.GetAttribute(AvatarAttributes.HOME_LOOKAT).AsVector3();
                response.HomePosition = avatar.GetAttribute(AvatarAttributes.HOME_POSITION).AsVector3();
                response.HomeRegionX = avatar.GetAttribute(AvatarAttributes.HOME_REGION_X).AsUInteger();
                response.HomeRegionY = avatar.GetAttribute(AvatarAttributes.HOME_REGION_Y).AsUInteger();
                response.LastName = "<" + account + ">";
                avatar.Attributes[AvatarAttributes.LAST_NAME] = OSD.FromString(response.LastName);
                response.Login = true;
                response.Message = base.defaultWelcomeMessage;
                response.SessionID = sessionID;
                response.SecureSessionID = UUID.Random();

                RegionInfo startRegion;
                Vector3 startPosition;
                // Find the simulator this avatar will start in
                if (LindenLoginHelper.TryGetStartingRegion(server, avatar, startLocationRequest, ref response, out startRegion, out startPosition))
                {
                    // If the avatar doesn't have a home region, set it to the current region
                    if (response.HomeRegionX == 0 && response.HomeRegionY == 0)
                    {
                        response.HomeRegionX = response.RegionX;
                        response.HomeRegionY = response.RegionY;
                    }

                    bool createDummyInventory = false;

                    // Contact the inventory server to get a skeleton listing of inventory folders and
                    // information about what the avatar is currently wearing
                    if (!LindenLoginHelper.TryGetInventoryInfo(server, avatar, ref response))
                    {
                        Service inventoryService;
                        Uri invCreateCap;
                        if (avatar.Services.TryGetValue(new Uri(CableBeachServices.FILESYSTEM), out inventoryService) &&
                            inventoryService.TryGetCapability(new Uri(CableBeachServices.FILESYSTEM_CREATE_FILESYSTEM), out invCreateCap))
                        {
                            // Fetch the inventory skeleton for this avatar
                            CreateInventoryMessage message = new CreateInventoryMessage();
                            message.Identity = identity;
                            message.AgentID = avatar.ID;
                            message.Name = response.FirstName + " " + response.LastName;
                            CapsClient capsRequest = new CapsClient(invCreateCap);
                            OSDMap responseMap = capsRequest.GetResponse(message.Serialize(), OSDFormat.Json, LindenLoginHelper.REQUEST_TIMEOUT) as OSDMap;

                            if (responseMap != null)
                            {
                                CreateInventoryReplyMessage reply = new CreateInventoryReplyMessage();
                                reply.Deserialize(responseMap);

                                //no status in message. only id. if id is not null, we can assume that message went through
                                if (reply.Success)
                                {
                                    //now get the skeleton again
                                    if (!LindenLoginHelper.TryGetInventoryInfo(server, avatar, ref response))
                                    {
                                        createDummyInventory = true;
                                    }
                                }
                                else
                                {
                                    Logger.ErrorFormat("[realXtendLogin] Could not create inventory for user, because {0}", reply.Message);
                                    createDummyInventory = true;
                                }
                            }
                        }
                    }

                    if (createDummyInventory)
                    {
                        //not found. insert dummy data
                        response.AgentInventory = new ArrayList(1);
                        Hashtable folderData = new Hashtable();
                        folderData["name"] = "Root";
                        folderData["parent_id"] = UUID.Zero;
                        folderData["version"] = 1;
                        folderData["type_default"] = (int)CableBeachMessages.AssetType.Object;
                        folderData["folder_id"] = new UUID("00000000-000f-0000-0000-000100bba000");

                        response.InventoryRoot = new UUID("00000000-000f-0000-0000-000100bba000");

                        response.AgentInventory.Add(folderData);
                        Logger.WarnFormat("[realXtendLogin] could not get inventory skeleton for user {0}. using dummy data", identity);

                        // Inventory Library Section
                        response.InventoryLibRoot = new UUID("00000112-000f-0000-0000-000100bba000");
                    }

                    // Contact the starting simulator to authorize this login
                    if (TryPrepareLogin(server, avatar, startRegion, startPosition, clientVersion, httpRequest.RemoteEndPoint.Address, ref response))
                    {
                        XmlRpcResponse resp = response.ToXmlRpcResponse();
                        Hashtable val = (Hashtable)resp.Value;
                        val["rex"] = "running rex mode";
                        return resp;
                    }

                }
                else
                {
                    return LindenLoginHelper.CreateLoginNoRegionResponse();
                }

                return LindenLoginHelper.CreateLoginInternalErrorResponse();

            }
        }

        private bool TryPrepareLogin(WorldServer server, Avatar avatar, RegionInfo startRegion, Vector3 startPosition,
            string clientVersion, System.Net.IPAddress clientIP, ref LindenLoginData response)
        {
            EnableClientMessage message = new EnableClientMessage();
            message.Identity = avatar.Identity;
            message.AgentID = avatar.ID;
            message.Attributes = avatar.Attributes;
            message.CallbackUri = null;
            message.ChildAgent = false;
            message.CircuitCode = LindenLoginHelper.CreateCircuitCode();
            message.ClientVersion = clientVersion;
            message.IP = clientIP;
            message.RegionHandle = startRegion.Handle;
            message.SecureSessionID = response.SecureSessionID;
            message.Services = avatar.Services.ToMessageDictionary();
            Dictionary<Uri, Uri> avStrgDict = new Dictionary<Uri, Uri>();
            avStrgDict.Add(RexAvatarAttributes.AVATAR_STORAGE_URL, avatar.Attributes[RexAvatarAttributes.AVATAR_STORAGE_URL].AsUri());
            message.Services.Add(RexAvatarAttributes.AVATAR_STORAGE_URL, avStrgDict);
            message.SessionID = response.SessionID;

            Uri enableClientCap;
            if (startRegion.Capabilities.TryGetValue(new Uri(CableBeachServices.SIMULATOR_ENABLE_CLIENT), out enableClientCap))
            {
                CapsClient request = (server.HttpCertificate != null) ?
                new CapsClient(enableClientCap, server.HttpCertificate) :
                new CapsClient(enableClientCap);

                OSDMap responseMap = request.GetResponse(message.Serialize(), OSDFormat.Json, LindenLoginHelper.REQUEST_TIMEOUT) as OSDMap;

                if (responseMap != null)
                {
                    EnableClientReplyMessage reply = new EnableClientReplyMessage();
                    reply.Deserialize(responseMap);

                    if (reply.SeedCapability != null)
                    {
                        Logger.Info("enable_client succeeded, sent circuit code " + message.CircuitCode + " and received seed capability " +
                            reply.SeedCapability + " from " + enableClientCap);

                        response.CircuitCode = message.CircuitCode;
                        response.SeedCapability = reply.SeedCapability.ToString();
                        return true;
                    }
                    else
                    {
                        Logger.Error("[LindenLoginHelper] enable_client call to region " + startRegion.Name + " for login from " + avatar.Identity +
                            " failed, did not return a seed capability");
                    }
                }
                else
                {
                    Logger.Error("[LindenLoginHelper] enable_client call to region " + startRegion.Name + " for login from " + avatar.Identity +
                        " failed, could not contact or invalid response");
                }
            }
            else
            {
                Logger.Error("[LindenLoginHelper] enable_client call failed, region " + startRegion.Name +
                    " does not have an enable_client capability");
            }

            return false;
        }

        private bool AuthenticateUser(string account, string sessionHash)
        {
            if (true) //m_checkSessionHash) //should in some point to switch using m_checkSessionHash. this value should be configurable
            {
                string actName = account.Split('@')[0];
                string actSrv = account.Split('@')[1];
                return RexLoginHelper.SimAuthenticationAccount(actName, sessionHash, actSrv);
            }
            else
            {
                return true;
            }
            return true;
        }
    }
}
